---
id: SourceCodeAnalysis
title: vue3 源码分析
---
## v-model 

#### v-model原理
在 Vue 3 中，v-model 是一个语法糖，可以让我们更方便地实现表单元素的双向绑定。其原理是通过将 v-model 属性转换为一个绑定 value 属性的 input 或 textarea 元素，以及监听 input 或 change 事件来更新组件的数据。

## reactive

在 Vue 3 中，reactive 是一个用于实现响应式数据的函数，可以将一个普通的 JavaScript 对象转换成响应式数据对象，其基本语法如下：

```js
import { reactive } from "vue";

const state = reactive({
  count: 0,
});
```

reactive 函数会对传入的对象进行代理，拦截对对象属性的读取和设置操作，并触发相应的响应式更新。  
具体来说，reactive 函数会使用 ES6 的 Proxy 对象对传入的对象进行代理，在代理对象上定义 get 和 set 拦截器，以监听属性的读取和设置操作，并触发相应的更新逻辑。

以下是 reactive 函数的源码实现：

```js
import { reactiveHandlers, readonlyHandlers } from "./baseHandlers";

function createReactiveObject(target, isReadonly, baseHandlers) {
  const proxy = new Proxy(target, baseHandlers);
  return proxy;
}

export function reactive(target) {
  return createReactiveObject(target, false, reactiveHandlers);
}
```

在 reactive 函数中，它首先会调用 createReactiveObject 函数来创建一个代理对象，然后将这个代理对象返回作为响应式数据对象。createReactiveObject 函数会根据传入的 isReadonly 参数来确定是否需要创建一个只读的代理对象，同时也会根据 baseHandlers 参数来确定代理对象的拦截器对象，这里的 reactiveHandlers 就是负责对对象进行响应式处理的拦截器对象。

以下是 createReactiveObject 函数的源码实现：

```js
import { mutableHandlers, readonlyHandlers } from "./baseHandlers";

function createReactiveObject(target, isReadonly, baseHandlers) {
  const proxy = new Proxy(target, baseHandlers);
  return proxy;
}

export function reactive(target) {
  return createReactiveObject(target, false, reactiveHandlers);
}

export function readonly(target) {
  return createReactiveObject(target, true, readonlyHandlers);
}

// reactiveHandlers 和 readonlyHandlers 是拦截器对象，分别用于创建响应式数据对象和只读数据对象
export const reactiveHandlers = mutableHandlers;
export const readonlyHandlers = Object.assign(
  {},
  mutableHandlers,
  readonlyObjHandlers
);

// readonlyObjHandlers 是只读数据对象的特殊拦截器，用于禁止设置属性值
const readonlyObjHandlers = {
  set(target, key) {
    console.warn(`Set operation on key "${key}" failed: target is readonly.`);
    return true;
  },
  deleteProperty(target, key) {
    console.warn(
      `Delete operation on key "${key}" failed: target is readonly.`
    );
    return true;
  },
};
```

在 reactive 函数中，它还引入了 reactiveHandlers 和 readonlyHandlers 这两个拦截器对象，它们分别用于创建响应式数据对象和只读数据对象。这两个拦截器对象实际上是基于 mutableHandlers 对象派生出来的，其中 mutableHandlers 对象定义了通用的拦截

## ref

- 在 Vue 3 中，ref 是一个用于在响应式系统中创建一个可变的基础数据类型的函数，其基本语法如下

```js
import { ref } from "vue";

const count = ref(0);
```

ref 函数返回一个包含一个响应式数据的对象，该对象有一个名为 value 的属性，可以通过该属性来读取或设置数据的值。

在 Vue 3 的源码中，ref 函数实际上是由 reactive 函数实现的。具体来说，ref 函数会将传入的初始值包装成一个包含 value 属性的对象，然后将这个对象传入 reactive 函数中进行响应式处理，最后返回这个包含 value 属性的响应式对象。这个响应式对象的 value 属性是一个“Proxy”，它可以拦截对属性的读取和设置操作，并触发相应的响应式更新。

```js
import { reactive } from "./reactive";

function ref(value) {
  // 将初始值包装成一个包含 value 属性的对象
  const obj = {
    value,
  };
  // 对这个对象进行响应式处理，并返回响应式对象
  const reactiveObj = reactive(obj);
  // 通过 getter 和 setter 来拦截对 value 属性的读取和设置操作
  return {
    get value() {
      return reactiveObj.value;
    },
    set value(newValue) {
      reactiveObj.value = newValue;
    },
  };
}
```

在实现中，ref 函数还使用了 reactive 函数来进行响应式处理。reactive 函数是 Vue 3 中响应式系统的核心实现，它会对传入的对象进行代理，拦截对对象属性的读取和设置操作，并触发相应的响应式更新。具体实现可以参考 reactive 函数的源码实现。

#### 为什么需要.value 进行访问

使用 new Proxy 对原始类型（例如字符串、数字、布尔值等）创建代理时，会抛出一个 TypeError。例如：

```js
const num = 42;
const proxy = new Proxy(num, {
  // Proxy 的处理函数
});
// TypeError: 42 is not an object
```
所以内部包了一层{value:'xxx'}



## watch

### watch 原理
其基本原理是通过 Proxy 和 Reflect API 来监听数据的变化，并在数据发生变化时触发回调函数。

具体来说，当您使用 watch 监听某个数据时，Vue 3会创建一个响应式 getter 函数来访问该数据，并将该 getter 函数传递给 Proxy 的 get 处理程序。当您访问该数据时，Proxy 会通过 Reflect API 捕获 get 操作，并返回该数据的值。在捕获到 get 操作后，Proxy 会将当前的 watcher 添加到该数据的依赖列表中。

然后，当该数据发生变化时，Vue 3会调用 Proxy 的 set 处理程序，并通过 Reflect API 捕获 set 操作。在捕获到 set 操作后，Proxy 会遍历该数据的依赖列表，依次执行每个 watcher 的回调函数。

总之，watch 的基本原理是通过 Proxy 和 Reflect API 来实现数据的响应式监听，并在数据发生变化时触发回调函数。这种机制使得 watch 在实现数据的监控和响应式更新时非常高效和灵活。
### watch 源码
在 Vue 3 中，watch 是一个用于监听数据变化并执行相应操作的 API。使用 watch API 可以监视指定的数据对象或表达式，并在其发生变化时执行回调函数。下面是 Vue 3 中 watch API 的源码分析。

```js
// 引入 watch 模块
import { effect, stop } from "@vue/reactivity";

// 定义 watch API
function watch(source, cb, options) {
  // 创建一个 reactive 对象
  const getter = () => source();
  const { flush, onTrack, onTrigger } = options || {};
  const reactiveEffect = effect(getter, {
    lazy: true,
    scheduler: () => {
      if (!reactiveEffect.active) {
        return;
      }
      if (flush === "sync") {
        cb();
      } else {
        watcher.dirty = true;
        if (onTrigger) {
          onTrigger(watcher);
        }
        queueJob(watchEffect, watcher);
      }
    },
  });

  // 创建一个 watcher 对象
  const watcher = {
    active: true,
    effect: reactiveEffect,
    deps: null,
    options: {
      flush,
      onTrack,
      onTrigger,
    },
    stop() {
      stop(reactiveEffect);
      if (watcher.options.onStop) {
        watcher.options.onStop();
      }
      watcher.active = false;
    },
  };

  if (options && options.immediate) {
    cb();
  }

  // 返回一个 stop 函数，用于取消 watcher
  return watcher.stop;
}
```

Vue 3 的 watch API 实际上是通过创建一个 reactive 对象和一个 watcher 对象来实现的。在执行 watch API 时，首先会创建一个 reactive 对象，并将传入的数据对象或表达式转化为一个 getter 函数，然后使用 effect 函数来创建一个 reactive 对象，并在 reactive 对象的 getter 函数中执行回调函数。在创建 reactive 对象时，可以通过 options 参数来指定刷新策略（即在哪个时机执行回调函数）、onTrack 回调函数和 onTrigger 回调函数等。

接着，watch API 会创建一个 watcher 对象，该对象包含 reactive 对象、deps（用于存储 reactive 对象的依赖关系）、选项（即 options 参数）和 stop 方法（用于取消 watcher）。在创建 watcher 对象时，会将 reactive 对象中的 getter 函数作为 watcher 对象的 effect 函数，并将 watcher 对象加入 reactive 对象的依赖中。

最后，watch API 返回一个 stop 函数，用于取消 watcher。当执行 stop 函数时，会先调用 stop 函数停止 reactive 对象的依赖追踪，然后将 watcher 对象标记为已取消。

总之，Vue 3 的 watch API 主要是通过创建一个 reactive 对象和一个 watcher 对象来实现的。在创建 reactive 对象时，会将传入的数据对象或表达式转化为一个 getter 函数，并使用 effect 函数来创建 reactive 对象。在创建 watcher 对象时，会将 reactive 对象中的 getter 函数作为 watcher 对象的 effect 函数，并将 watcher 对象加

## watchEffect

watchEffect 是 Vue.js 3.x 中新增的 API，它是用来创建一个响应式的函数，该函数会自动追踪它所引用的响应式数据，并在响应式数据变化时重新执行。下面我们来分析一下 watchEffect 的源码实现。

watchEffect 的实现代码位于 packages/reactivity/src/effect.ts 中，其中最核心的是 effect 函数：

```js
export function effect(
  fn: () => void,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
  // ...
}
```

effect 函数接收一个函数作为第一个参数，并返回一个 ReactiveEffect 对象。在 watchEffect 中，我们传入的就是一个函数。

ReactiveEffect 对象是用来管理响应式函数的，它有两个核心属性：fn 和 scheduler。其中，fn 属性就是我们传入的函数，而 scheduler 属性则是用来控制响应式函数的执行时机。

```js
export interface ReactiveEffect<T = any> {
  (): T
  _isEffect: true
  active: boolean
  raw: () => T
  deps: Dep[]
  options: ReactiveEffectOptions
  allowRecurse: boolean
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
```

接下来，让我们来看一下 effect 函数的具体实现。首先，effect 函数会根据传入的 options 参数创建一个 ReactiveEffectOptions 对象，该对象包含了响应式函数的一些选项配置。

```js
export interface ReactiveEffectOptions {
  lazy?: boolean
  computed?: boolean
  scheduler?: (job: ReactiveEffect) => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
  allowRecurse?: boolean
}

```

其中，lazy 属性表示是否需要延迟执行响应式函数；computed 属性表示是否为计算属性；scheduler 属性表示响应式函数的执行调度器；onTrack 和 onTrigger 属性表示在追踪和触发响应式数据时的回调函数；onStop 属性表示在响应式函数停止执行时的回调函数；allowRecurse 属性表示是否允许响应式函数递归调用。

接下来，effect 函数会创建一个响应式函数对象，并返回它。在创建响应式函数对象时，它会先执行一次响应式函数，并在执行过程中，记录响应式数据与响应式函数之间的依赖关系。

```js
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
  effect();
}
return effect;
```

其中，createReactiveEffect 函数会返回一个具有 fn 和 scheduler 属性的响应式函数对象。

```js
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions

```

在 Vue.js 中，watchEffect 函数用于创建一个响应式的效果，它会在它的依赖项更改时自动重新运行。而 flush 选项则用于控制响应式效果在何时执行以及何时将其视图更新。

flush 选项有三种可选值："pre"、"post"和"sync"。默认值为"post"。

flush: "post"：这是默认值，表示效果将在当前渲染队列刷新后执行。也就是说，Vue 会先完成所有的 DOM 更新，然后才会执行 watchEffect 函数，以确保更新已完成。
flush: "pre"：这表示效果将在 DOM 更新之前执行。这意味着在执行 DOM 更新之前，您可以做出响应式效果。
flush: "sync"：这表示效果将立即同步执行，并立即更新 DOM。这是最快的刷新模式，但它可能会影响应用程序的性能。
您可以在创建 watchEffect 时使用 flush 选项来自定义其执行行为。如果您需要在 DOM 更新之前或之后做一些操作，那么可以使用 flush: "pre"或 flush: "post"选项。如果您需要实时更新 DOM，请使用 flush: "sync"选项。


## nextTick

Vue 3 中的 nextTick 方法用于在 DOM 更新之后执行回调函数。在使用 nextTick 方法时，Vue 会将回调函数加入一个队列中，在下一次 DOM 更新完成后执行队列中的回调函数。下面是 Vue 3 的 nextTick 方法的源码分析：

```js
// 引入 scheduler 模块
import { queueJob } from "./scheduler";

// 定义 nextTick 方法
export function nextTick(fn?: () => void) {
  return fn ? queueJob(fn) : new Promise((resolve) => queueJob(resolve));
}
```

Vue 3 的 nextTick 方法实际上是通过调用 queueJob 函数来实现的。queueJob 函数会将传入的回调函数加入一个任务队列中，然后在下一次 DOM 更新完成之后执行队列中的所有回调函数。具体来说，queueJob 函数会先判断当前是否在更新队列中，并根据情况将回调函数加入更新队列中或直接执行回调函数。

在 Vue 3 中，更新队列中的任务都是由 scheduler 模块管理的。scheduler 模块会对任务队列中的任务进行排序和去重，保证任务按照正确的顺序执行，并且不会重复执行相同的任务。在每次 DOM 更新时，scheduler 模块会按照任务的优先级执行任务队列中的任务。

在实际使用中，可以通过 nextTick 方法来确保在 DOM 更新之后执行回调函数，例如：

```js
// 等待 DOM 更新完成之后再执行回调函数
nextTick(() => {
  console.log("DOM 已更新");
});
```

总之，Vue 3 的 nextTick 方法主要是通过调用 queueJob 函数将回调函数加入更新队列中，然后在下一次 DOM 更新完成之后执行回调函数。
